package com.liujit.pay.wx.kit;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.LogFactory;
import com.liujit.pay.common.enums.RequestMethod;
import com.liujit.pay.common.util.PayUtil;
import com.liujit.pay.common.util.RsaUtil;
import com.liujit.pay.wx.enums.PayApi;
import com.liujit.pay.wx.enums.PayDomain;

import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @description: 微信支付工具类
 * @author: liujun
 * @create: 2021-02-20 15:05
 */
public class WxPayKit {

    /**
     * 认证类型
     */
    private static final String SCHEMA = "WECHATPAY2-SHA256-RSA2048";

    /**
     * 创建签名
     *
     * @param signMessage 待签名参数
     * @param keyPath     key.pem 证书路径
     * @return
     */
    public static String createSign(List<String> signMessage, String keyPath) {
        // 构建签名参数
        String buildSignMessage = buildSignMessage(signMessage);
        return createSign(buildSignMessage, keyPath);
    }

    /**
     * 获取请求地址
     *
     * @param payApi {@link PayApi} 可通过 PayApi 来获取
     * @return
     */
    public static String getReqUrl(PayApi payApi) {
        return PayDomain.CHINA.getDomain().concat(payApi.getApi());
    }

    /**
     * 获取请求地址
     *
     * @param payApi {@link PayApi} 可通过 PayApi 来获取
     * @return
     */
    public static String getReqUrl(String payApi) {
        return PayDomain.CHINA.getDomain().concat(payApi);
    }

    /**
     * 获取请求地址
     *
     * @param payApi {@link PayApi} 可通过 PayApi 来获取
     * @param param  请求参数
     * @return
     */
    public static String getReqUrl(String payApi, Map<String, Object> param) {
        StringBuffer sbf = new StringBuffer();
        param.forEach((k, v) -> {
            sbf.append("&" + k + "=" + v);
        });
        return payApi + sbf.toString().replaceFirst("&", "?");
    }

    /**
     * 构建接口所需的 Authorization
     *
     * @param method   {@link RequestMethod} 请求方法
     * @param payApi   {@link PayApi} 可通过 PayApi 来获取
     * @param mchId    商户Id
     * @param serialNo 商户证书序列号
     * @param keyPath  key.pem 证书路径
     * @param body     接口请求参数
     * @return {@link String} 返回所需的 Authorization
     * @throws Exception 异常信息
     */
    public static String buildAuthorization(RequestMethod method, String payApi, String mchId, String serialNo,
                                            String keyPath, String body) {
        String nonceStr = PayUtil.generateNonceStr();
        String timestamp = PayUtil.generateTimestamp();
        // 构建签名参数
        String buildSignMessage = buildSignMessage(method, payApi, timestamp, nonceStr, body);
        String signature = createSign(buildSignMessage, keyPath);
        // 根据平台规则生成请求头 authorization
        return getAuthorization(mchId, serialNo, nonceStr, timestamp, signature);
    }

    /**
     * 构建接口所需的 Authorization
     *
     * @param method   {@link RequestMethod} 请求方法
     * @param payApi   {@link PayApi} 可通过 PayApi 来获取
     * @param mchId    商户Id
     * @param serialNo 商户证书序列号
     * @param keyPath  key.pem 证书路径
     * @return {@link String} 返回所需的 Authorization
     * @throws Exception 异常信息
     */
    public static String buildAuthorization(RequestMethod method, String payApi, String mchId, String serialNo,
                                            String keyPath) {
        String nonceStr = PayUtil.generateNonceStr();
        String timestamp = PayUtil.generateTimestamp();
        // 构建签名参数
        String buildSignMessage = buildSignMessage(method, payApi, timestamp, nonceStr, "");
        String signature = createSign(buildSignMessage, keyPath);
        // 根据平台规则生成请求头 authorization
        return getAuthorization(mchId, serialNo, nonceStr, timestamp, signature);
    }

    /**
     * 构造签名串
     *
     * @param method    {@link RequestMethod} 请求方式
     * @param payApi    请求接口
     * @param body      请求报文主体
     * @param timestamp 时间戳
     * @param nonceStr  请求随机串
     * @return 待签名字符串
     */
    private static String buildSignMessage(RequestMethod method, String payApi, String timestamp, String nonceStr, String body) {
        List<String> list = new ArrayList<>();
        list.add(method.getMethod());
        list.add(payApi);
        list.add(timestamp);
        list.add(nonceStr);
        list.add(body);
        return buildSignMessage(list);
    }

    /**
     * 构造签名串
     *
     * @param signMessage 待签名的参数
     * @return 构造后带待签名串
     */
    private static String buildSignMessage(List<String> signMessage) {
        if (signMessage == null || signMessage.size() <= 0) {
            return null;
        }
        StringBuffer sbf = new StringBuffer();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }

    /**
     * 接口创建签名
     *
     * @param signMessage 待签名的参数
     * @param keyPath     key.pem 证书路径
     * @return 生成签名
     * @throws Exception 异常信息
     */
    private static String createSign(String signMessage, String keyPath) {
        if (StrUtil.isEmpty(signMessage)) {
            return "";
        }
        try {
            // 获取商户私钥
            PrivateKey privateKey = getPrivateKey(keyPath);
            // 生成签名
            return RsaUtil.encryptByPrivateKey(signMessage, privateKey);
        } catch (Exception e) {
            LogFactory.get().error("商户证书内容读取错误. 错误信息: {}. 证书路径: {}", e.getMessage(), keyPath);
            return "";
        }
    }

    /**
     * 获取商户私钥
     *
     * @param keyPath key.pem 证书路径
     * @return {@link PrivateKey} 商户私钥
     * @throws Exception 异常信息
     */
    private static PrivateKey getPrivateKey(String keyPath) throws Exception {
        String originalKey = FileUtil.readUtf8String(keyPath);
        String privateKey = originalKey
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");

        return RsaUtil.loadPrivateKey(privateKey);
    }

    /**
     * 获取授权认证信息
     *
     * @param mchId     商户号
     * @param serialNo  商户API证书序列号
     * @param nonceStr  请求随机串
     * @param timestamp 时间戳
     * @param signature 签名值
     * @return 请求头 Authorization
     */
    private static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature) {
        Map<String, String> params = new HashMap<>(5);
        params.put("mchid", mchId);
        params.put("serial_no", serialNo);
        params.put("nonce_str", nonceStr);
        params.put("timestamp", timestamp);
        params.put("signature", signature);
        return SCHEMA.concat(" ").concat(PayUtil.createLinkString(params, ",", false, true));
    }
}
